﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace Kohonen
{
    public class Kohonen
    {
        #region Atributos / Propriedades
        /// <summary>
        /// Taxa de aprendizagem da rede
        /// </summary>
        private double taxa = 0.1;

        /// <summary>
        /// Lista dos Neurônios dentro do Mapa de Kohonen
        /// </summary>
        public List<Neuronio> lstNeuronios { get; set; }

        #endregion

        /// <summary>
        /// Construtor
        /// </summary>
        /// <param name="qtdNeuronios">Quantidade de Neurônios dentro do Mapa</param>
        public Kohonen(int qtdNeuronios)
        {
            lstNeuronios = new List<Neuronio>();

            for (int i = 0; i < qtdNeuronios; i++)
                lstNeuronios.Add(new Neuronio(taxa, 2,false));
        }

        #region Faz o Desenho dos Neuronios na tela
        public Image Desenhar(Image imagem)
        {
            Bitmap img = (Bitmap)imagem.Clone();

            try
            {

                Graphics g = Graphics.FromImage(img);

                for (int i = 0; i < lstNeuronios.Count; i++)
                {
                    if (lstNeuronios[i].QtdVitorias == 0)
                        g.FillEllipse(new SolidBrush(Color.Red), (int)lstNeuronios[i].W[0] - 4, (int)lstNeuronios[i].W[1] - 4, 8, 8);
                    else
                        g.FillEllipse(new SolidBrush(Color.Blue), (int)lstNeuronios[i].W[0] - 4, (int)lstNeuronios[i].W[1] - 4, 8, 8);
                }
                g.Dispose();

            }
            catch 
            {
                //Erro ao desenhar na tela
            }

            return img;
        }
        #endregion


        #region Métodos
        /// <summary>
        /// Método para o treinamento
        /// </summary>
        /// <param name="dado">Dado aleatório para o treinamento</param>
        public void Run(Dado dado)
        {
            int indice = GetWin(dado);
            AtualizarNeuronios(indice, dado, (int)(lstNeuronios.Count * 0.2));
        }


        /// <summary>
        /// Faz a atualização do neurônio vencedor juntamente com sua vizinhança
        /// </summary>
        /// <param name="indice">Indice do neurônio vencedor</param>
        /// <param name="dado">Dado escolhido para o treinamento</param>
        /// <param name="nivel">Nivel de vizinhança utilizada</param>
        private void AtualizarNeuronios(int indice, Dado dado, int nivel)
        {
            double[] valor = new double[2];
            valor[0] = dado.X;
            valor[1] = dado.Y;
            //Atualiza os pesos do neuronio vencedor
            lstNeuronios[indice].Atualizar(valor, 1);
            //Incrementa a quantidade de vezes que o neuronio venceu
            lstNeuronios[indice].QtdVitorias++;            
            //Atualiza os vizinhos, respeitando o nivel de vinzinhança determinada, sendo nivel/2 para direita e nivel/2 para esquerda

            for (int i = 0; i < nivel/2; i++)
            {
                int indiceD = EncontrarIndiceParaAtualizar(indice, lstNeuronios.Count, i + 1, false);
                int indiceE = EncontrarIndiceParaAtualizar(indice, lstNeuronios.Count, i + 1, true);

                lstNeuronios[indiceD].Atualizar(valor, 2 * (1 + i));
                lstNeuronios[indiceE].Atualizar(valor, 2 * (1 + i));
            }
                        
        }

        /// <summary>
        /// Método para encontrar o indice para a atualização da vizinhança
        /// </summary>
        /// <param name="indiceAtual">Indice do Neurônio Vencedor</param>
        /// <param name="totalElementos">Quantidade de Neurônios</param>
        /// <param name="qtdPassos">Valor do deslocamento</param>
        /// <param name="direcaoEsquerda">Direção da vizinhança</param>
        /// <returns></returns>
        private int EncontrarIndiceParaAtualizar(int indiceAtual, int totalElementos, int qtdPassos, bool direcaoEsquerda)
        {
            //Encontra o indice para a atualização da vizinhança
            //Lembramos que temos uma topologia circular de neurônios

            if (direcaoEsquerda)
            {
                if ((indiceAtual - qtdPassos) < 0)
                {
                    indiceAtual = totalElementos + (indiceAtual - qtdPassos);
                }
                else
                {
                    indiceAtual = indiceAtual - qtdPassos;
                }
            }
            else
            {
                if ((indiceAtual + qtdPassos) >= totalElementos)
                {
                    indiceAtual = qtdPassos + indiceAtual - totalElementos;
                }
                else
                {
                  indiceAtual = qtdPassos + indiceAtual;  
                }
            }


            return indiceAtual;
        }

        /// <summary>
        /// Método que retorna o indice do Neurônio vencedor pela distância euclidiana
        /// </summary>
        /// <param name="dado">Dado escolhido para o treinamento</param>
        /// <returns></returns>
        private int GetWin(Dado dado)
        {
            //Retorna o Neurônio vencedor pela distância

            double[] valor = new double[2];
            double aux = double.MaxValue;
            int indice = 0;
            

            valor[0] = dado.X;
            valor[1] = dado.Y;
                        
            for (int i = 0; i < lstNeuronios.Count; i++)
            {
                double distancia = lstNeuronios[i].Distancia(valor);
                if ((distancia < aux) && (distancia >0))
                {
                    aux = distancia;
                    indice = i;
                }
            }       
     
            return indice;
        }
        #endregion
    }
}
